﻿using Microsoft.Build.Construction;
using Sasa.QualityTools.CodeAnalysis.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Sasa.QualityTools.CodeAnalysis.Common;
using System.Globalization;

namespace Sasa.QualityTools.CodeAnalysis.Core.Models
{
    public class MSBuildProject
    {
        private static readonly string CodeAnalysisCommandPath = Path.Combine("$(APPDATA)", CommonConstants.ProductName, Core.Constants.CodeAnalysisCommandFileName);

        private static readonly string CodeAnalyisOnBuildCondition =
            Text.Format("'$(BuildingInsideVisualStudio)' == 'true' And '$(EnableCodeAnalysisOnBuild)' == 'true' And Exists('$(TargetPath)') And Exists('{0}')", CodeAnalysisCommandPath);

        private static readonly string MSBuildTargetsPath
            = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES"), "MSBuild", CommonConstants.ProductName, "FxCopIntegrator.targets");


        private ProjectRootElement root;

        private bool originalEnableCodeAnalysisOnBuild;

        private Dictionary<MSBuildProperty, bool> originalValues;

        public bool CanIntegrateMSBuild { get; set; }

        public bool EnableCodeInspectionOnBuild { get; set; }

        public bool EnableCodeMetricsCalculationOnBuild { get; set; }

        public bool EnableCodeAnalysisOnBuild { get; set; }

        public ICollection<string> DependencyDirectories { get; private set; }


        private MSBuildProject(string path)
        {
            root = ProjectRootElement.Open(path);

            CanIntegrateMSBuild = File.Exists(MSBuildTargetsPath);
            EnableCodeInspectionOnBuild = Convert.ToBoolean(GetPropertyValue(MSBuildProperty.EnableCodeInspectionOnBuild), CultureInfo.InvariantCulture);
            EnableCodeMetricsCalculationOnBuild = Convert.ToBoolean(GetPropertyValue(MSBuildProperty.EnableCodeMetricsCalculationOnBuild), CultureInfo.InvariantCulture);
            EnableCodeAnalysisOnBuild = Convert.ToBoolean(GetPropertyValue(MSBuildProperty.EnableCodeAnalysisOnBuild), CultureInfo.InvariantCulture);

            DependencyDirectories = new List<string>();

            originalValues = new Dictionary<MSBuildProperty, bool>();
            originalValues[MSBuildProperty.EnableCodeInspectionOnBuild] = EnableCodeInspectionOnBuild;
            originalValues[MSBuildProperty.EnableCodeMetricsCalculationOnBuild] = EnableCodeMetricsCalculationOnBuild;
            originalValues[MSBuildProperty.EnableCodeAnalysisOnBuild] = EnableCodeAnalysisOnBuild;
            originalEnableCodeAnalysisOnBuild = EnableCodeAnalysisOnBuild;
        }


        public static MSBuildProject Load(string path)
        {
            return new MSBuildProject(path);
        }

        public void Save()
        {
            if (!IsEdited())
            {
                return;
            }

            SetPropertyValue(MSBuildProperty.EnableCodeInspectionOnBuild, EnableCodeInspectionOnBuild.ToString().ToLower());
            // Maybe code metrics calculation on build time gives little benefit without CI.
            // So FxCop Inegrator doesn't change the EnableCodeMetricsCalculationOnBuild property.
            //SetPropertyValue(MSBuildProperty.EnableCodeMetricsCalculationOnBuild, EnableCodeMetricsCalculationOnBuild.ToString().ToLower());
            SetPropertyValue(MSBuildProperty.EnableCodeAnalysisOnBuild, EnableCodeAnalysisOnBuild.ToString().ToLower());

            var targetsPath = MSBuildTargetsPath.Replace(Environment.GetEnvironmentVariable("PROGRAMFILES"), "$(PROGRAMFILES)");
            if (root.Imports.Count(o => o.Project == targetsPath) == 0)
            {
                var import = root.AddImport(targetsPath);
                import.Condition = Text.Format("Exists('{0}')", targetsPath);
            }

            if (EnableCodeAnalysisOnBuild)
            {
                var target = FindOrCreateAfterBuildTarget(root);
                var exec = FindCodeAnalysisOnBuildTask(target);
                if (exec == null)
                {
                    exec = target.AddTask("Exec");
                    exec.Condition = CodeAnalyisOnBuildCondition;
                }
                var dependencies = String.Join(" ", DependencyDirectories.Select(o => Text.Format(@"/d:""{0}""", o)));
                exec.SetParameter("Command", Text.Format(@"""{0}"" /f:""$(TargetPath)"" /s:""$(SolutionDir)\"" /p:""$(ProjectDir)\"" {1}", CodeAnalysisCommandPath, dependencies));
            }

            root.Save();
        }

        private bool IsEdited()
        {
            if (EnableCodeInspectionOnBuild != originalValues[MSBuildProperty.EnableCodeInspectionOnBuild])
            {
                return true;
            }
            if (EnableCodeMetricsCalculationOnBuild != originalValues[MSBuildProperty.EnableCodeMetricsCalculationOnBuild])
            {
                return true;
            }
            if (EnableCodeAnalysisOnBuild != originalValues[MSBuildProperty.EnableCodeAnalysisOnBuild])
            {
                return true;
            }
            return false;
        }

        private static ProjectTargetElement FindOrCreateAfterBuildTarget(ProjectRootElement root)
        {
            var target = root.Targets.FirstOrDefault(o => o.Name == "AfterBuild");
            if (target == null)
            {
                target = root.AddTarget("AfterBuild");
            }
            return target;
        }

        private static ProjectTaskElement FindCodeAnalysisOnBuildTask(ProjectTargetElement target)
        {
            return target.Tasks.FirstOrDefault(o => o.Name == "Exec" && o.Condition == CodeAnalyisOnBuildCondition);
        }

        private string GetPropertyValue(MSBuildProperty property)
        {
            return GetPropertyValue(property.ToString());
        }

        private string GetPropertyValue(string name)
        {
            var property = root.Properties.FirstOrDefault(o => o.Name == name);
            return (property != null) ? property.Value : null;
        }

        private void SetPropertyValue(MSBuildProperty property, string value)
        {
            SetPropertyValue(property.ToString(), value);
        }

        private void SetPropertyValue(string name, string value)
        {
            var property = root.Properties.FirstOrDefault(o => o.Name == name);
            if (property == null)
            {
                root.AddProperty(name, value);
            }
            else
            {
                property.Value = value;
            }
        }


        private enum MSBuildProperty
        {
            EnableCodeInspectionOnBuild,
            EnableCodeMetricsCalculationOnBuild,
            EnableCodeAnalysisOnBuild,
        }
    }
}
